package ge;

import ge.standardsymbols.ConstantSymbol;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;

import org.apache.log4j.Logger;

/**
 * Holds type information about a symbol and some helper methods for
 * manipulating the symbols.
 * 
 * @author Nathaniel Troutman
 */
public class SymbolType {
	private static final Logger LOG = Logger.getLogger(ProductionRule.class);

	public boolean is_nonterminal = false;
	public boolean is_constant = false;
	private Method _resolve_method = null;

	public Class<?> clazz = null;

	private Method _production_rule_method;

	public ConstantSymbol _constant_symbol;

	public SymbolType(Class<?> symbol_type) {
		clazz = symbol_type;

		if (NonTerminal.class.isAssignableFrom(symbol_type)) {
			is_nonterminal = true;
			setSetProductionRuleMethod(symbol_type);
			setResolveMethod(symbol_type);
		}
	}

	public SymbolType(ConstantSymbol constantSymbol) {
		clazz = constantSymbol.getClass();
		is_constant = true;
		_constant_symbol = constantSymbol;
	}

	/**
	 * If this SymbolType represents a non-terminal resolve it and return the
	 * resulting expression. Otherwise, return null.
	 * 
	 * @param indices
	 * @param depth
	 * @return
	 */
	public Expression resolve(Iterator<Integer> indices, int depth) {
		Expression exp = null;

		if (is_nonterminal) {
			try {
				exp = (Expression) _resolve_method.invoke(null, new Object[] {
						clazz, indices, depth });
			} catch (IllegalArgumentException e) {
				LOG.fatal("Error resolving Symbol:" + clazz, e);
				System.exit(0);
			} catch (IllegalAccessException e) {
				LOG.fatal("Error resolving Symbol:" + clazz, e);
				System.exit(0);
			} catch (InvocationTargetException e) {
				LOG.fatal(
						"Error resolving production rule! Failed to invoke method["
								+ _resolve_method + "]", e);
				System.exit(0);
			}
			catch (Exception e) {
				LOG.error("Error resolving production rule!", e);
			}
		}

		return exp;
	}

	/**
	 * Get the method that will be used to resolve this non-terminal.
	 * 
	 * @param nonterminal
	 */
	private void setResolveMethod(Class<?> nonterminal) {
		try {
			_resolve_method = nonterminal.getMethod("resolve", new Class<?>[] {
					Class.class, Iterator.class, int.class });
		} catch (Exception e) {
			LOG.error("Error finding resolve() method!", e);
		}
	}

	/**
	 * Get the method that will set the production rule this nonterminal will
	 * use.
	 * 
	 * @param nonterminal
	 */
	private void setSetProductionRuleMethod(Class<?> nonterminal) {
		try {
			_production_rule_method = nonterminal.getMethod(
					"setProductionRule", new Class<?>[] { Class.class,
							ProductionRule.class });
		} catch (Exception e) {
			LOG.error("Error finding setProductionRule() method!", e);
		}
	}

	/**
	 * Set the production rule of this symbol (must be a nonterminal, otherwise
	 * this method will do nothing) to the given production rule.
	 * 
	 * @param rule
	 */
	public void setProductionRule(ProductionRule rule) {
		try {
			_production_rule_method.invoke(null, new Object[] { clazz, rule });
		} catch (Exception e) {
			LOG.error("Error calling setProductionRule()!", e);
		}
	}
	
	public String toString() {
		return "SymbolType[" + clazz.getSimpleName() + ", isNonTerminal="
				+ is_nonterminal + "]";
	}
}
